#include "WiiCISIOPrivateInclude.h"
#include "WiiCISIOPublicInclude.h"
#include "WiiCISIORootWindows.h"

namespace WiiCIS {
	namespace IO {
		RootWindows::RootWindows()
			: isManuallyPaired(false) {
		}

		RootWindows::~RootWindows() {
			while(isLocked())
				Sleep(1);

			Disconnect();
			Sleep(500);

			while(isLocked())
				Sleep(1);
		}

		void RootWindows::Connect() {
			if (isConnected() || isLocked())
				return;

			Lock();
			_beginthread(ConnectThread, 0, this);
		}

		void RootWindows::Disconnect() {
			if (!isConnected() || isLocked())
				return;

			Lock();
			_beginthread(DisconnectThread, 0, this);
		}

		void __cdecl RootWindows::ConnectThread(void *io) {
			RootWindows *ioRoot = static_cast<RootWindows *>(io);

			ioRoot->FindNext();
			ioRoot->ScanDevices();
			ioRoot->Unlock();
		}

		void __cdecl RootWindows::DisconnectThread(void *io) {
			RootWindows *ioRoot = static_cast<RootWindows *>(io);

			ioRoot->CloseDeviceHandles();
			ioRoot->Unlock();
		}

		void RootWindows::FindNext() {
#if WIICIS_COMPILER == WIICIS_COMPILER_MSVC
			if (isConnected())
				return;

			BLUETOOTH_DEVICE_SEARCH_PARAMS searchParameters = CreateBluetoothSearchParameters();
			BLUETOOTH_DEVICE_INFO deviceInfo = CreateBluetoothDeviceInfo();

			HANDLE searchHandle = BluetoothFindFirstDevice(&searchParameters, &deviceInfo);
			if (!searchHandle)
				return;

			do {
				if (deviceInfo.fRemembered && deviceInfo.fConnected) {
					isManuallyPaired = true;
				} else if (deviceInfo.fRemembered && !deviceInfo.fConnected) {
					SetBluetoothServiceState(&deviceInfo, false);
					BluetoothRemoveDevice(&deviceInfo.Address);
				} else if (deviceInfo.ulClassofDevice == 0x2504) { // No, I have no idea where that number came from.
					SetBluetoothServiceState(&deviceInfo, true);
					Sleep(12000); // Wait for windows to finish installing the driver. Because windows sucks like that.
				} else {
					continue;
				}

				break;
			} while (BluetoothFindNextDevice(searchHandle, &deviceInfo));

			activeDeviceInfo = deviceInfo;
#endif
		}

		void RootWindows::ScanDevices() {
			GUID deviceId = GetDeviceId();
			HDEVINFO deviceInfo = SetupDiGetClassDevs(&deviceId, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);

			bool foundDevice = false;
			int index = 0;

			while (!foundDevice) {
				SP_DEVICE_INTERFACE_DATA deviceData;
				deviceData.cbSize = sizeof(deviceData);
				if (!SetupDiEnumDeviceInterfaces(deviceInfo, NULL, &deviceId, index, &deviceData))
					break;

				index++;

				DWORD dataSize;
				SetupDiGetDeviceInterfaceDetail(deviceInfo, &deviceData, NULL, 0, &dataSize, NULL);
				PSP_DEVICE_INTERFACE_DETAIL_DATA detailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(dataSize);
				detailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);

				if (SetupDiGetDeviceInterfaceDetail(deviceInfo, &deviceData, detailData, dataSize, NULL, NULL)) {
					HANDLE discoveredHandle = CreateFile(detailData->DevicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ,	NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);

					if (TryOpenDevice(discoveredHandle))
						foundDevice = true;
				}

				delete detailData;
			}

			SetupDiDestroyDeviceInfoList(deviceInfo);
		}

		bool RootWindows::TryOpenDevice(HANDLE discoveredHandle) {
			if (discoveredHandle == INVALID_HANDLE_VALUE)
				return false;

			HIDD_ATTRIBUTES attr;
			attr.Size = sizeof(attr);
			HidD_GetAttributes(discoveredHandle, &attr);

			if (attr.VendorID != VendorID || attr.ProductID != ProductID) {
				CloseHandle(discoveredHandle);
				return false;
			}

			deviceHandle = discoveredHandle;
			hidOverlap.hEvent = CreateEvent(NULL, 1, 1, "");
			hidOverlap.Offset = 0;
			hidOverlap.OffsetHigh = 0;

			setConnected(true);

			return true;
		}

		void RootWindows::CloseDeviceHandles() {
			if (!isConnected() || !deviceHandle)
				return;

			setConnected(false);

			CloseHandle(deviceHandle);
			deviceHandle = NULL;
			ResetEvent(hidOverlap.hEvent);

#if WIICIS_COMPILER == WIICIS_COMPILER_MSVC
			if (!isManuallyPaired && activeDeviceInfo.ulClassofDevice == 0x2504)
				BluetoothRemoveDevice(&activeDeviceInfo.Address);
#endif
		}

		void RootWindows::Read(byte *buffer) {
			if (!isConnected())
				return;

			byte newBuffer[32] = {0};
			DWORD bytesRead;
			if (!ReadFile(deviceHandle, newBuffer, sizeof(newBuffer), &bytesRead, &hidOverlap)) { // Partial read.
				DWORD lastError = GetLastError();

				if (lastError == ERROR_HANDLE_EOF || lastError == ERROR_DEVICE_NOT_CONNECTED) {
					CloseDeviceHandles();
					setConnected(false);
					return;
				}

				switch (WaitForSingleObject(hidOverlap.hEvent, 50)) {
				case WAIT_TIMEOUT:
					CancelIo(deviceHandle);
					ResetEvent(hidOverlap.hEvent);
				case WAIT_FAILED: // Falls through
					return;
				}

				if (!GetOverlappedResult(deviceHandle, &hidOverlap, &bytesRead, false))
					return;
			}

			memcpy(buffer, newBuffer, sizeof(newBuffer));
			ResetEvent(hidOverlap.hEvent);
		}

		void RootWindows::Write(byte *buffer, int length) {
			if (!isConnected())
				return;

			HidD_SetOutputReport(deviceHandle, buffer, length);
		}
	}
}